home *** CD-ROM | disk | FTP | other *** search
/ Mac-Source 1994 July / Mac-Source_July_1994.iso / C and C++ / Utilities / Winter Shell 1.0d2 / Source / Libraries / SegmentLib / SegmentLib.c next >
Encoding:
C/C++ Source or Header  |  1994-01-19  |  11.8 KB  |  381 lines  |  [TEXT/KAHL]

  1. /* Functions for managing segments for applications or code resources.
  2.     This file must be in the main segment of the program.
  3.     
  4.     94/01/18 aih
  5.     - THINK C uses two different jump table formats: one while debugging,
  6.     the other for the final application. We determine which jump table
  7.     format is being used and adjust access to the jump table accordingly.
  8.     
  9.     94/01/04 aih
  10.     - scans stack for segments in use, then only unloads segments
  11.     that aren't in use
  12.     
  13.     93/11/22 aih
  14.     - adapted for new method of installing patches
  15.     - added check for memory before loading code segment
  16.         
  17.     93/03/26 AIH
  18.     - Added patch to LoadSeg
  19.     
  20.     93/03/22 AIH
  21.     - Added functions for automatically loading and unloading segments,
  22.     while keeping track of which segments are in use
  23.     
  24.     93/03/19 AIH
  25.     - Added function to unload all of the segments
  26.     
  27.     93/03/10 AIH
  28.     - Added special case for loading THINK C code segments
  29.     
  30.     91/05/06 Ari Halberstadt (AIH)
  31.     - Fixed potential problem with use of ResLoad (casting short to Boolean) */
  32.  
  33. #include <Traps.h>
  34. #include "AsmLib.h"
  35. #include "LowMemLib.h"
  36. #include "MemoryLib.h"
  37. #include "PatchLib.h"
  38. #include "SegmentLib.h"
  39. #include "SortLib.h"
  40.  
  41. /*----------------------------------------------------------------------------*/
  42. /* patch to loadseg */
  43. /*----------------------------------------------------------------------------*/
  44.  
  45. /* For its own reasons, perhaps related to using FAR code, THINK C seems
  46.     to patch the _LoadSeg trap. THINK C also makes code resources locked.
  47.     Unfortunately, this prevents _LoadSeg from calling MoveHHi on segments,
  48.     even if the low-memory global SegHiEnable is non-zero. Having code
  49.     segments low in memory leads to severe heap fragmentation when pointers
  50.     are allocated above the code segments. This patch will load the resource,
  51.     move it to the top of the heap, and lock it there. This patch will also
  52.     trigger an exception if the resource couldn't be loaded or if there isn't
  53.     enough memory to load the resource. */
  54. static pascal void PatchLoadSeg(PatchType *patch, short seg)
  55. {
  56.     Handle code;
  57.     
  58.     require(GetResLoad());
  59.     SetResLoad(false);
  60.     code = GetResource('CODE', seg);
  61.     SetResLoad(true);
  62.     FailNILRes(code);
  63.     if (! *code)  {
  64.         /* Check that memory is available to load the resource. Segment
  65.             unloading is disabled since unloading segments from within
  66.             this patch crashes the mac eventually.
  67.             (program_note: Why? Is the segment loader not reentrant? moving segments?) */
  68.         Boolean unload = SegmentsUnloadEnable(false);
  69.         Boolean available = MemAvailableNoCushion(SizeResource(code));
  70.         SegmentsUnloadEnable(unload);
  71.         if (! available)
  72.             FailOSErr(memFullErr);
  73.     }
  74.     SetResAttrs(code, GetResAttrs(code) & ~resLocked);
  75.     FailResError();
  76.     LoadResource(code);
  77.     FailResError();
  78.     if (GetSegHiEnable()) {
  79.         MoveHHi(code);
  80.         HLock(code);
  81.     }
  82. }
  83.  
  84. pascal void LoadSegTrap(short seg) = _LoadSeg; 
  85.  
  86. /* load the segment */
  87. static void LoadSeg(short seg)
  88. {
  89.     asm {
  90.                 move.w seg, -(sp)
  91.                 bra.s @1     /* call LoadSeg */
  92.                 bra.s @2     /* LoadSeg returns here */
  93.                 nop             /* fill up two bytes */
  94.         @1:    LoadSegTrap
  95.         @2:
  96.     }
  97. }
  98.  
  99. /*----------------------------------------------------------------------------*/
  100. /* segment unloading */
  101. /*----------------------------------------------------------------------------*/
  102.  
  103. /* Unload all segments without return addresses on the stack. This
  104.     assumes that register a6 is used as a stack frame pointer for all
  105.     external functions and for all functions whose address is taken.
  106.     Under THINK C 5.0.x, this is true if you use the force_frame option
  107.     to force generation of a stack frame. Note that a stack frame is
  108.     not generated for functions that take no parameters and have no
  109.     local variables, but there are very very few functions like that
  110.     in my code, and the ones that do exist probably have no impact
  111.     on this segmentation strategy since they are called by a function
  112.     with a stack frame in that segment.
  113.     
  114.     Based on information in IM-II, p60-62. */
  115.     
  116. /*----------------------------------------------------------------------------*/
  117. /* jump table utilities */
  118. /*----------------------------------------------------------------------------*/
  119.  
  120. #if defined(THINK_C) && __option(far_code)
  121.     /* The jump table format is (probably) different when using far
  122.         code, so we prevent compilation of this library. There are two
  123.         solutions: either modify this library to support far code, or
  124.         disable unloading of segments. */
  125.     #error jump table access has not been tested for far code
  126. #endif
  127.  
  128. #define JT_ENTRY_SIZE            (8)    /* size of a jump table entry */
  129. #define JT_ENTRY_HEADER_SIZE    (2)    /* the two bytes preceding the jump table
  130.                                                     entry's _LoadSeg or JMP instruction */
  131.  
  132. /* We must determine whether we're debugging so that we can calculate the
  133.     correct jump table offset (see the function SegmentJTOffset). We can tell
  134.     that we're debugging since the type of the application's resource file won't
  135.     be 'APPL' (the current resource file when this function is first called is
  136.     assumed to be the application's resource file). */
  137. static short JTOffsetMultiplier(void)
  138. {
  139.     static short multiplier = 0;
  140.     FCBPBRec fcb;
  141.     Str255 name;
  142.     FInfo info;
  143.     
  144.     if (! multiplier) {
  145.         multiplier = 1;
  146.         #ifdef THINK_C
  147.             fcb.ioCompletion = NULL;
  148.             fcb.ioNamePtr = name;
  149.             fcb.ioVRefNum = 0;
  150.             fcb.ioRefNum = CurResFile();
  151.             fcb.ioFCBIndx = 0;
  152.             FailOSErr(PBGetFCBInfo(&fcb, false));
  153.             FailOSErr(HGetFInfo(fcb.ioVRefNum, fcb.ioFCBParID, fcb.ioNamePtr, &info));
  154.             if (info.fdType != 'APPL')
  155.                 multiplier = JT_ENTRY_SIZE;
  156.         #endif /* THINK_C */
  157.     }
  158.     ensure(multiplier == 1 || multiplier == JT_ENTRY_SIZE);
  159.     return(multiplier);
  160. }
  161.  
  162. /* Given a pointer to a code resource, return the offset into the jump
  163.     table of the segment's first routine. According to IM-II, the first two
  164.     bytes of the code resource contain the offset into the jump table.
  165.     However, In THINK C, when debugging (i.e., the application hasn't been
  166.     built yet), the offset must be multiplied by 8; it is not clear to me
  167.     why THINK C does this, but it seems to patch the segment loader. */
  168. static short SegmentJTOffset(Ptr code)
  169. {
  170.     return(*(short *) code * JTOffsetMultiplier());
  171. }
  172.  
  173. /* given a pointer to a code resource return the segment's first jump
  174.     table entry */
  175. static Ptr JTEntry(Ptr code)
  176. {
  177.     return(GetCurrentA5() + GetCurJTOffset() + SegmentJTOffset(code));
  178. }
  179.  
  180. /* return segment number for a jump table entry */
  181. static short JTNumber(void *entry)
  182. {
  183.     short *jmp = entry;
  184.     short seg = 0;
  185.  
  186.     if (jmp[1] == ASM_JMP)
  187.         seg = jmp[0]; /* loaded segment */
  188.     else if (jmp[1] == ASM_MOVE && jmp[3] == _LoadSeg)
  189.         seg = jmp[2]; /* unloaded segment */
  190.     return(seg);
  191. }
  192.  
  193. /*----------------------------------------------------------------------------*/
  194. /* segment unloading stuff */
  195. /*----------------------------------------------------------------------------*/
  196.  
  197. /* addresses of loaded segments */
  198. typedef struct {
  199.     Ptr start;                                /* start address of segment */
  200.     Ptr end;                                    /* one past last address of segment */
  201.     Boolean active;                        /* true if segment is in use */
  202. } SegmentType;
  203.  
  204. static Boolean gUnloadEnable;            /* true enables segment unloading */
  205.  
  206. /* comparison function for qksort */
  207. static int sort_compare(const void *a, const void *b)
  208. {
  209.     if ((*(SegmentType *) a).start < (*(SegmentType *) b).start) return(-1);
  210.     if ((*(SegmentType *) a).start > (*(SegmentType *) b).start) return(1);
  211.     return(0);
  212. }
  213.  
  214. /* comparison function for bsearch */
  215. static int search_compare(const void *key, const void *data)
  216. {
  217.     if ((Ptr) key < (*(SegmentType *) data).start) return(-1);
  218.     if ((Ptr) key >= (*(SegmentType *) data).end) return(1);
  219.     return(0);
  220. }
  221.  
  222. /* from THINK C's "bsearch.c" */
  223. static void *bsearch(const void *key, const void *base, size_t n, size_t size,
  224.     int compare(const void *key, const void *data))
  225. {
  226.     register size_t i = 0, j;
  227.     void *guess;
  228.     int k;
  229.     
  230.     while (i < n) {
  231.         j = (i + n - 1) >> 1;
  232.         guess = (char *) base + j * size;
  233.         if ((k = compare(key, guess)) == 0)
  234.             return(guess);
  235.         if (k < 0)
  236.             n = j;
  237.         else
  238.             i = j + 1;
  239.     }
  240.     return(NULL);
  241. }
  242.  
  243. /* return the segment table */
  244. static SegmentType *SegmentTable(void)
  245. {
  246.     static SegmentType *segments;
  247.  
  248.     if (! segments) {
  249.         /* allocate segment table */
  250.         short nseg = CountResources('CODE');
  251.         FailResError();
  252.         check(nseg > 0);
  253.         segments = (SegmentType *) NewPtr(sizeof(SegmentType) * nseg);
  254.         FailNIL(segments);
  255.     }
  256.     return(segments);
  257. }
  258.  
  259. /* get addresses of loaded locked segments, return number of segments */
  260. static short SegmentsLoaded(SegmentType *segments)
  261. {
  262.     Boolean load;        /* saved ResLoad flag */
  263.     short nloaded;        /* number of loaded segments */
  264.     short nseg;            /* number of segments */
  265.     short i;                /* index to segment */
  266.  
  267.     /* get number of code resources */
  268.     nseg = CountResources('CODE');
  269.     FailResError();
  270.     check(nseg > 0);
  271.  
  272.     check(GetPtrSize((Ptr) segments) == nseg * sizeof(SegmentType));
  273.     
  274.     /* Get addresses of all loaded segments. A loaded segment must not
  275.         be purged and must be locked. These are segments which will need
  276.         to call UnloadSeg for, but only if they don't contain any active code.  */
  277.     load = GetResLoad();
  278.     SetResLoad(false);
  279.     nloaded = 0;
  280.     for (i = 1; i <= nseg; i++) {
  281.         Handle code = GetIndResource('CODE', i);
  282.         if (code && *code && (HGetState(code) & (1<<7)) != 0) {
  283.             segments[nloaded].start = StripAddress(*code);
  284.             segments[nloaded].end = StripAddress(*code) + SizeResource(code);
  285.             segments[nloaded].active = false;
  286.             nloaded++;
  287.         }
  288.     }
  289.     SetResLoad(load);
  290.     return(nloaded);
  291. }
  292.  
  293. /* trace stack, marking segments containing return addresses */
  294. static void SegmentsActive(SegmentType *segments, short nloaded)
  295. {
  296.     SegmentType *seg; /* segment found by bsearch() */
  297.     Ptr stacktop;        /* top of stack */
  298.     Ptr stackbottom;    /* bottom of stack */
  299.     Ptr frame;            /* stack frame pointer (starting from register a6) */
  300.     Ptr retaddress;    /* return address on stack */
  301.     short i;
  302.  
  303.     /* sort list of loaded segments */
  304.     qksort(segments, nloaded, sizeof(SegmentType), sort_compare);
  305.     
  306.     /* search list using binary search for each return address */
  307.     stacktop = GetCurStackBase();
  308.     asm { move.l sp, stackbottom }
  309.     asm { move.l a6, frame }
  310.     while (stackbottom <= frame && frame < stacktop) {
  311.         retaddress = *((Ptr *) (frame + sizeof(Ptr)));
  312.         seg = bsearch(retaddress, segments, nloaded,
  313.                           sizeof(SegmentType), search_compare);
  314.         if (seg)
  315.             seg->active = true;
  316.         frame = *(Ptr *) frame;
  317.     }
  318.     if (frame) {
  319.         /* Didn't trace entire stack, so something's wrong and
  320.             we shouldn't unload any segments. This could occur,
  321.             for instance, when called from certain ROM call-backs,
  322.             such as a heap's grow zone function. This is unfortunate,
  323.             since a heap grow zone function is the ideal place to
  324.             unload segments, but the OS doesn't always use the stack
  325.             frame convention based on register a6. */
  326.         for (i = 0; i < nloaded; i++)
  327.             segments[i].active = true;
  328.     }
  329. }
  330.     
  331.     
  332. /* unload all inactive segments */
  333. static void SegmentsUnloadInactive(const SegmentType *segments, short nloaded)
  334. {
  335.     short i;
  336.     
  337.     for (i = 0; i < nloaded; i++) {
  338.         if (! segments[i].active)
  339.             UnloadSeg(JTEntry(segments[i].start) + JT_ENTRY_HEADER_SIZE);
  340.     }
  341. }
  342.  
  343. /* enable/disable unloading of segments, return old value */
  344. Boolean SegmentsUnloadEnable(Boolean enable)
  345. {
  346.     Boolean enabled;
  347.     
  348.     enabled = gUnloadEnable;
  349.     gUnloadEnable = enable;
  350.     return(enabled);
  351. }
  352.  
  353. /* unload all inactive segments */
  354. void SegmentsUnload(void)
  355. {
  356.     SegmentType *segments;    /* table of segment addresses */
  357.     short nloaded;                /* number of loaded segments */
  358.     
  359.     if (gUnloadEnable) {
  360.         segments = SegmentTable();
  361.         nloaded = SegmentsLoaded(segments);
  362.         SegmentsActive(segments, nloaded);
  363.         SegmentsUnloadInactive(segments, nloaded);
  364.     }
  365. }
  366.  
  367. /* load the segment containing the function */
  368. void SegmentLoad(void *fun)
  369. {
  370.     LoadSeg(JTNumber((Ptr) fun - JT_ENTRY_HEADER_SIZE));
  371. }
  372.  
  373. /* initialize segments; this function must be in the main segment */
  374. void SegmentsInit(void)
  375. {
  376.     (void) PatchBegin(PatchLoadSeg, _LoadSeg, sizeof(short), 0, NULL);
  377.     (void) JTOffsetMultiplier(); /* initialize jump table entry multiplier */
  378.     (void) SegmentTable(); /* allocate segment table */
  379.     (void) SegmentsUnloadEnable(true);
  380. }
  381.